/*
 * TcpClient.cpp
 *
 *  Created on: 2016年5月13日
 *      Author: terry
 */

#include "TcpClient.h"
#include "ClockTime.h"

#ifdef WIN32
#else
#include <signal.h>

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/tcp.h>
#endif //



namespace av
{

TcpClient::TcpClient():
		m_ip(),
		m_port(),
		m_sendBufferSize(1024*1024),
		m_sentSize(),
        m_sink(),
        m_connected(),
        m_connectionTime(),
        m_connectionDropped(),
        m_reconnectEnabled(true)
{
}

TcpClient::~TcpClient()
{
	disconnect();
}

bool TcpClient::connect(const std::string& ip, int port)
{
	m_ip = ip;
	m_port = port;

    m_connectionTime = comn::ClockTime::getTime();
    fireEvent(kConnecting);

	return doConnect(m_ip, m_port);
}

void TcpClient::disconnect()
{
	m_socket.close();

    m_connectionTime = 0;
    m_connected = false;
}

bool TcpClient::isConnected()
{
	return m_socket.isOpen();
}

comn::Socket& TcpClient::getSocket()
{
	return m_socket;
}

bool TcpClient::write(ByteBufferPtr& buffer)
{
    if (m_connected)
    {
        if (doWrite(buffer))
        {
            return true;
        }
        else
        {
            int ret = checkWriteClose(m_socket.getHandle(), 0);
            if (ret <= 0)
            {
                m_connected = false;
                m_connectionDropped = true;
                fireEvent(kDisconnected);
            }

            /// 丢弃该块
            return false;
        }
    }
    else
    {
        int ret = checkWriteClose(m_socket.getHandle(), 200);
        if (ret > 0)
        {
            m_connected = true;
            fireEvent(kConnected);

            return doWrite(buffer);
        }
        else
        {
            int64_t clock = comn::ClockTime::getTime();
            int64_t elapse = clock - m_connectionTime;
            if (elapse > 5000)
            {
                if (m_connectionDropped && !m_reconnectEnabled)
                {
                    /// disable reconnect
                }
                else
                {
                    m_connectionTime = comn::ClockTime::getTime();

                    fireEvent(kConnecting);
                    doConnect(m_ip, m_port);
                }
            }
        }
        return false;
    }
}

bool TcpClient::doWrite(ByteBufferPtr& buffer)
{
    if (flushBuffer())
    {
        m_buffer = buffer;
        m_sentSize = 0;
        return flushBuffer();
    }
    return false;
}

bool TcpClient::equals(const std::string& ip, uint16_t port) const
{
	return (m_ip == ip) && (m_port == port);
}

void TcpClient::setSendBuffer(int size)
{
	m_sendBufferSize = size;
}

bool TcpClient::doConnect(const std::string& ip, uint16_t port)
{
	m_socket.close();

	m_socket.open(SOCK_STREAM);
	m_socket.setNonblock(true);
	m_socket.setReuse();
	m_socket.setSendBufferSize(m_sendBufferSize);

	m_socket.connect(ip.c_str(), port);

    bool connected = m_socket.checkConnect(10) > 0;
    if (connected)
    {
        m_connected = true;
        fireEvent(kConnected);
    }

    return connected;
}

bool TcpClient::flushBuffer()
{
	if (!m_buffer)
	{
		return true;
	}

	if (m_sentSize >= (int)m_buffer->size())
	{
		m_sentSize = 0;
		m_buffer.reset();
		return true;
	}

	uint8_t* data = m_buffer->data();
	size_t size = m_buffer->size();

	data += m_sentSize;
	size -= m_sentSize;

	int ret = m_socket.send((char*)data, size, 0);
	if (ret <= 0)
	{
		return false;
	}

	m_sentSize += ret;

	if (m_sentSize >= (int)m_buffer->size())
	{
		m_sentSize = 0;
		m_buffer.reset();
		return true;
	}
	return false;
}

void TcpClient::setSink(Sink* sink)
{
    m_sink = sink;
}

void TcpClient::fireEvent(int event)
{
    if (m_sink)
    {
        m_sink->onTcpEvent(this, event);
    }
}

int TcpClient::checkWriteClose(socket_t fd, int millsec)
{
    fd_set rset;
    FD_ZERO(&rset);
    FD_SET(fd, &rset);

    fd_set wset;
    FD_ZERO(&wset);
    FD_SET(fd, &wset);

    fd_set xset;
    FD_ZERO(&xset);
    FD_SET(fd, &xset);

    timeval* ptv = NULL;
    timeval tv = { millsec / 1000, (millsec % 1000) * 1000 };
    ptv = (millsec >= 0) ? (&tv) : NULL;

    int ret = select((int)fd + 1, &rset, &wset, &xset, ptv);
    if (ret >= 1)
    {
        if (FD_ISSET(fd, &xset))
        {
            ret = -1;
        }
        else if (FD_ISSET(fd, &rset))
        {
            char buffer[8];
            int bytes = ::recv(fd, buffer, 8, MSG_PEEK);
            if (bytes <= 0)
            {
                ret = -1;
            }
        }
        else if (FD_ISSET(fd, &wset))
        {
            ret = 1;
        }
    }
    return ret;
}

void TcpClient::enableReconnect(bool enabled)
{
    m_reconnectEnabled = enabled;
}

} /* namespace av */
